package com.ushine.web.aop;

import com.alibaba.fastjson.JSONArray;
import com.ushine.web.base.model.base.BaseBO;
import com.ushine.web.component.page.PageRequestQuery;
import com.ushine.web.component.page.ParamsQuery;
import com.ushine.web.component.page.QueryVO;
import com.ushine.web.log.annotation.SystemControllerLog;
import com.ushine.web.log.model.Log;
import com.ushine.web.log.service.SystemLogService;
import com.ushine.web.log.utils.HttpContextUtils;
import com.ushine.web.log.utils.IPUtils;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiModelProperty;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.io.Serializable;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Optional;

/**
 * @Author: yls
 * @Date: 2020/10/27 20:47
 * @Description: 日志切面工具类
 * @Version 1.0
 */
@Component
public class SystemLogAopUtil {

    @Resource
    private SystemLogService systemLogService;

    Object getSystemLog(ProceedingJoinPoint joinPoint) throws Throwable {
        int logParamMaxSize = 5000;
        // 日志
        Log log = new Log();
        // 从切面织入点处通过反射机制获取织入点处的方法
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        // 获取切入点所在的方法
        Method method = signature.getMethod();
        // 获取类名
        Object target = joinPoint.getTarget();
        Class<?> aClass = target.getClass();
        Api apiAnnotation = aClass.getAnnotation(Api.class);
        if (apiAnnotation != null) {
            String[] tags = apiAnnotation.tags();
            log.setClassName(Arrays.toString(tags));
        }

        // 获取方法名
        ApiOperation apiOperationAnnotation = method.getAnnotation(ApiOperation.class);
        if (apiOperationAnnotation != null) {
            String value = apiOperationAnnotation.value();
            value = Optional.ofNullable(value).orElse("");
            log.setMethodName(value);
        }

        // 获取操作
        SystemControllerLog systemControllerLog = method.getAnnotation(SystemControllerLog.class);
        if (systemControllerLog != null) {
            int value = systemControllerLog.operation();
            //保存获取的操作
            log.setOperation(value);
        }
        // 获取请求的类名
        String className = joinPoint.getTarget().getClass().getName();
        // 获取请求的方法名
        String methodName = method.getName();
        log.setMethod(className + "." + methodName);
        // 请求的参数
        Object[] args = joinPoint.getArgs();
        //获取所有参数上的注解
        Annotation[][] parameterAnnotations = signature.getMethod().getParameterAnnotations();
        // 将参数所在的数组转换成json
        LocalVariableTableParameterNameDiscoverer paramNames = new LocalVariableTableParameterNameDiscoverer();
        String[] params = paramNames.getParameterNames(method);
        JSONArray fieldsInfo = new JSONArray();
        // 遍历所有参数
        listParams(args, parameterAnnotations, params, fieldsInfo);
        /*
         * 屏蔽参数为[]的情况
         */
        String logParam = fieldsInfo.toJSONString();
        // 空数组
        String nullStr = "[]";
        if (nullStr.equals(logParam)) {
            logParam = "";
        }
        log.setParams(logParam);
        /*
         * 获取请求体内容
         */
        HttpServletRequest request = HttpContextUtils.getHttpServletRequest();
        // 获取请求地址
        String requestUri = request.getRequestURI();
        log.setRequestUri(requestUri);
        // 获取请求方式
        String requestMethod = request.getMethod();
        log.setRequestMethod(requestMethod);
        // 获取请求IP
        String ipAddr = IPUtils.getIpAddress(request);
        log.setIp(ipAddr);
        Object proceed;
        try {
            // 执行增强后的方法
            long startTime = System.currentTimeMillis();
            proceed = joinPoint.proceed();
            final long endTime = System.currentTimeMillis();
            log.setExecuteTime(endTime - startTime);
            log.setExceptionLog("无异常");
            log.setType("info");
        } catch (Throwable throwable) {
            /*
             * 过滤无权限操作
             */
            log.setExceptionLog(throwable.getMessage());
            log.setType("Err");
            throw throwable;
        } finally {
            String resultParams = log.getParams();
            if (resultParams.length() > logParamMaxSize) {
                log.setParams(resultParams.substring(0, logParamMaxSize - 1));
            }
            this.saveLog(log);
        }
        return proceed;
    }

    /**
     * 功能描述:
     * 〈 遍历所有的参数 〉
     *
     * @param args                 参数数组
     * @param parameterAnnotations 注解二维数组
     * @param params               参数数组
     * @param fieldsInfo           字段信息
     * @return : void
     * @author : yls
     * @date : 2022/2/7 18:00
     */
    private void listParams(Object[] args, Annotation[][] parameterAnnotations, String[] params, JSONArray fieldsInfo) throws IllegalAccessException {
        for (Annotation[] parameterAnnotation : parameterAnnotations) {
            int paramIndex = ArrayUtils.indexOf(parameterAnnotations, parameterAnnotation);
            JSONArray paramsArr = new JSONArray();
            String paramValue = "单个参数";
            for (Annotation annotation : parameterAnnotation) {
                String paramName = params[paramIndex];
                Object arg = args[paramIndex];
                // 原生参数
                if (annotation instanceof ApiParam) {
                    ApiParam apiParam = (ApiParam) annotation;
                    paramsArr.add(toLogString(paramName, apiParam.value(), arg.toString()));
                    paramValue = apiParam.value();
                }
                if (annotation instanceof RequestBody) {
                    Boolean baseType = isBaseType(arg);
                    if (arg instanceof QueryVO) {
                        JSONArray tempJsonArr = getQueryJsonArray((QueryVO) arg);
                        fieldsInfo.addAll(tempJsonArr);
                    } else if (arg instanceof PageRequestQuery) {
                        fieldsInfo.addAll(getDeclaredFieldsInfo(arg));
                    } else if (arg instanceof Collection) {
                        fieldsInfo.add(toLogString(paramName, paramValue, arg.toString()));
                    } else if (Boolean.TRUE.equals(baseType)) {
                        fieldsInfo.add(toLogString(paramName, paramValue, arg.toString()));
                    } else if (arg instanceof String) {
                        fieldsInfo.add(toLogString(paramName, paramValue, arg.toString()));
                    } else if (arg instanceof BaseBO) {
                        fieldsInfo.addAll(getDeclaredFieldsInfo(arg));
                    } else if (arg instanceof Serializable) {
                        fieldsInfo.addAll(getDeclaredFieldsInfo(arg));
                    }
                }
                if (annotation instanceof RequestParam || annotation instanceof PathVariable) {
                    fieldsInfo.addAll(paramsArr);
                }
            }
        }
    }

    /**
     * 功能描述:
     * 〈 解析QueryVO对象 〉
     *
     * @param arg 查询参数
     * @return : org.json.simple.JSONArray
     * @author : yls
     * @date : 2021/04/27 23:00
     */
    private JSONArray getQueryJsonArray(QueryVO arg) throws IllegalAccessException {
        JSONArray tempJsonArr = new JSONArray();
        Object v = arg.getV();
        JSONArray vFieldsInfo = new JSONArray();
        if (v instanceof ParamsQuery) {
            vFieldsInfo.add(((ParamsQuery) v).toLogString());
        } else if (v instanceof List) {
            JSONArray tempArr = getJsonArrayByList(v);
            vFieldsInfo.addAll(tempArr);
        } else if (v instanceof BaseBO) {
            vFieldsInfo = getDeclaredFieldsInfo(v);
        } else if (v instanceof Serializable) {
            vFieldsInfo = getDeclaredFieldsInfo(v);
        }

        tempJsonArr.addAll(vFieldsInfo);
        return tempJsonArr;
    }

    /**
     * 功能描述:
     * 〈解析list〉
     *
     * @param v 解析查询参数
     * @return : org.json.simple.JSONArray
     * @author : yls
     * @date : 2021/04/27 23:27
     */
    private static JSONArray getJsonArrayByList(Object v) {
        JSONArray tempArr = new JSONArray();
        List<ParamsQuery> obj;
        obj = (List) v;
        if (obj != null && !obj.isEmpty()) {
            Object tempObj = obj.get(0);
            if (tempObj instanceof ParamsQuery) {
                List<ParamsQuery> paramsQueries = (List<ParamsQuery>) v;
                paramsQueries.forEach(paramsQuery -> tempArr.add(paramsQuery.toLogString()));
            } else {
                tempArr.add(v.toString());
            }
        }
        return tempArr;
    }

    /**
     * 功能描述:
     * 〈获取类中的所有属性名称及注解内容〉
     *
     * @param instance 对象
     * @return : com.alibaba.fastjson.JSONArray
     * @author : yls
     * @date : 2022/2/8 14:42
     */
    private static JSONArray getDeclaredFieldsInfo(Object instance) throws IllegalAccessException {
        JSONArray jsonArray = new JSONArray();
        Class<?> clazz = instance.getClass();
        Field[] fields = clazz.getDeclaredFields();
        for (Field field : fields) {
            field.setAccessible(true);
            Class<?> type = field.getType();
            // 获取注解的值
            boolean annotationPresent = field.isAnnotationPresent(ApiModelProperty.class);
            if (annotationPresent && !List.class.equals(type)) {
                // 获取注解值
                String remark = field.getAnnotation(ApiModelProperty.class).value();
                String fieldName = field.getName();
                Object fieldValueObj = field.get(instance);
                String fieldValue = Optional.ofNullable(fieldValueObj).orElse("").toString();
                if (!StringUtils.isBlank(fieldValue)) {
                    jsonArray.add(toLogString(fieldName, remark, fieldValue));
                }
            }

            if (List.class.equals(type)) {
                JSONArray arrayByList = getJsonArrayByList(field.get(instance));
                jsonArray.addAll(arrayByList);
            }
        }
        return jsonArray;
    }


    private static String toLogString(String fieldName, String remark, String fieldValue) {
        return fieldName + ":" + remark + ":" + fieldValue;
    }

    /**
     * 判断参数是否为基本数据类型
     *
     * @param o 参数
     * @return 是基本数据类型返回true，否则返回false
     */
    private static Boolean isBaseType(Object o) {
        String s = o.getClass().getSimpleName().toLowerCase();
        switch (s) {
            case "boolean":
            case "character":
            case "byte":
            case "short":
            case "integer":
            case "long":
            case "float":
            case "double":
                return true;
            default:
                return false;
        }
    }

    /**
     * 功能描述：
     * 通过mybatis保存日志
     *
     * @param log 存入日志信息的实体类
     */
    @Async
    public void saveLog(Log log) {
        //调用service保存SysLog实体类到数据库
        systemLogService.save(log);
    }
}
